package sweet.mvc.core

import javax.servlet.{ ServletContext }
import javax.servlet.http.{HttpServletRequest=>Req, HttpServletResponse=>Resp, _}
import java.io.Writer
import freemarker.template.{ Configuration, TemplateModel, ObjectWrapper }
import freemarker.ext.servlet.{ FreemarkerServlet, AllHttpScopesHashModel }
import freemarker.ext.beans.{ BeansWrapper }
import sweet.helper.{ Logger, JSystem, StringHelper }
import sweet.mvc.api._

/** Process a request and and response */
trait RequestProcessor{
  def process(req: Req, resp: Resp): Unit
}

/** A parser the digest the request uri that needed for Sweet application to route process. */
trait ReqRouteParser {
  def parse(req: Req): ReqRoute
}
  
/** Parse URI that match the following format: 
 * <code>/contextName/dispatcherName/controllerName/handlerName</code>
 * <code>/contextName/dispatcherName/controllerName /defaultHandlerName</code>
 * <code>/contextName/dispatcherName /defaultControllerName/defaultHandlerName</code>
 * <code>/dispatcherName/controllerName/handlerName</code>
 * 
 * This URI is resulted in mapping the dispatcher servlet with <code>/dispaatcherName/&#42;</code> (asterisk) in web.xml.
 */
class InPathDispatcherNameParser(
  val defaultControllerName: String, 
  val defaultHandlerName: String) extends ReqRouteParser with Logger {
    
  import StringHelper._
  
  def parse(req: Req): ReqRoute = {
    val uri = req.getRequestURI
    logger.debug("Parsing uri "+uri)
    val Array(contextName, dispatcherName, rest@_*) = uri.fastSplit("/")
    
    var controllerName = defaultControllerName
    var handlerName = defaultHandlerName
    var query = ""
    if(rest.length>0 && rest(0) != ""){
      val rest1 = rest.toList
      controllerName = "/"+rest1.head
      val rest2 = rest1.tail
      if(rest2.length>0 && rest2(0) != ""){
        handlerName = "/"+rest2.head
        val rest3 = rest2.tail
        if(rest3.length>0 && rest3(0) != ""){
          query = rest3.mkString("/","/","")
        }
      }
    }
    val reqRoute = new ReqRoute("/"+contextName, "/"+dispatcherName, controllerName, handlerName, query)
    logger.debug("reqRoute "+ reqRoute)
    reqRoute
  }
}

/** Parse URI that match the following: 
 * <code>contextName/controllerName/handlerName.MYEXTENSION</code>
 *
 * This URI is resulted in mapping the dispatcher servlet with "*.MYEXTENSION" in web.xml.
 */
class ExtensionDispatcherNameParser extends ReqRouteParser {
  def parse(req: Req): ReqRoute = {
    throw new Exception("not yet implemented.")
  }
}

/** ViewRender perform the output to response object using the req as models. 
 *
 * View is usuage reanderd from a template file using the given name. */
trait ViewRender{
  val viewPrefix: String
  val viewSuffix: String
  def contentType: String
  def render(name: String, reqRoute: ReqRoute, req: Req, resp: Resp): Unit
}

/** Render output using the Freemarker template engine.
 * 
 * We are extending FreemarkerServlet to reuse some of it's provided methods. Since members are private
 * this class, we can only use method that are exposing parameters where we can pass our own. Currently 
 * we are overriding/using deduceLocale and createModel methods.
 *
 * The render method is shorter version of original FreemarkerServlet's processRequest method.
 * 
 */
class FreemarkerViewRender(
  val contentType: String,
  val viewPrefix: String,
  val viewSuffix: String,
  val servletContext: ServletContext,
  val freemarkerConfig: Configuration) 
  extends FreemarkerServlet with ViewRender with Logger {
  
  protected lazy val scalaWrapper = freemarkerConfig.getObjectWrapper
  
  //original FMServlet constant, they were private, so have to redefine them here.
  val KEY_APPLICATION = "Application"
  val KEY_SESSION = "Session"
  val KEY_REQUEST = "Request"
  
  val LOWCASE_KEY_APPLICATION = "application"
  val LOWCASE_KEY_SESSION = "session"
  val LOWCASE_KEY_REQUEST = "request"
  
  val prefixViewNameWithControllerName = true
    
  /** Default content type is text/html */
  def this(prefix: String, suffix: String, ctx: ServletContext, cfg: Configuration){
    this("text/html", prefix, suffix, ctx, cfg)
  }
  
  /** Implementation of rendering a view - delegate to Freemarker template process */
  def render(name: String, reqRoute: ReqRoute, req: Req, resp: Resp): Unit = {
    
    //viewName = prefix+dynamic+sufix
    val viewName = 
      viewPrefix + 
      (if(prefixViewNameWithControllerName) reqRoute.controllerName+name else name) + 
      viewSuffix
      
    logger.debug("Get template name " + viewName)
    val template = freemarkerConfig.getTemplate(viewName, deduceLocale(name, req, resp))
    
    val customContentType = template.getCustomAttribute("content_type")
    val ctype = if (customContentType != null) customContentType.asInstanceOf[String] else contentType
    logger.debug("Setting response content type " + ctype)
    resp.setContentType(ctype)
        
    logger.debug("Preparing models for templates")
    val model = createModel(scalaWrapper, servletContext, req, resp)
    
    logger.debug("Rendering template.")
    template.process(model, resp.getWriter)
  }  
  
  /** Override to simulate a servlet instance so createModel can create objects properly. */
  override def getServletContext = servletContext 
  
  /** Override to return local from our own config obj. */
  override def deduceLocale(name: String, req: Req, resp: Resp) = freemarkerConfig.getLocale
  
  /** Generate model that searchable in Request, Session, Application scope. */
  override def createModel(scalaWrapper: ObjectWrapper, servletContext: ServletContext, 
    req: Req, resp: Resp): TemplateModel = {
      
    val ret = super.createModel(scalaWrapper, servletContext, req, resp)
    val model: AllHttpScopesHashModel = ret.asInstanceOf[AllHttpScopesHashModel]
    
    // Duplicate existing FM models into lowercase key names. */
    model.putUnlistedModel(LOWCASE_KEY_APPLICATION, model.get(KEY_APPLICATION))
    model.putUnlistedModel(LOWCASE_KEY_SESSION, model.get(KEY_SESSION))
    model.putUnlistedModel(LOWCASE_KEY_REQUEST, model.get(KEY_REQUEST))
        
    model //return model
  }
}

/**
 * Provides a default request processing workflow as follows:
 * 
 *    1. parse incoming URL according to a reqRouteParser rule
 *    2. find a controller that mapped to incoming URL
 *    3. find a handler from the above controller that match to incoming URL
 *    4. invoke the handler and receive a view object back.
 *    5. export any models in view object into request scope
 *    6. invoke viewRender.render(view, req) that produce output to response                  
 *       
 */
class SweetRequestProcessor(
  val reqRouteParser: ReqRouteParser,
  val controllers: scala.collection.Map[String, Controller],
  val viewEngine: ViewRender)
  extends RequestProcessor with ServletTypes with Logger {
    
  import Constants._
  
  def process(req: Req, resp: Resp): Unit = {
    val reqRoute = reqRouteParser.parse(req)
        
    //Internal method just for the req process.
    def render(view: View){
      //add models to request scope and return view
      for((k,v) <- view.models){
        logger.debug("Adding view model "+k+" to request scope.")
        req.setAttribute(k, v)
      }
      
      logger.debug("Invoking view render with "+view.name)
      viewEngine.render(view.name, reqRoute, req, resp)
    }
    
    //add reqRoute to req scope     
    req.setAttribute(REQ_ROUTE_KEY, reqRoute)
                         
    logger.info("Request received " + reqRoute)
    val t1 = JSystem.now  
    
    controllers.get(reqRoute.controllerName) match {
      case Some(controller) => {
        logger.debug("Processing with controller " + reqRoute.controllerName)
        controller.findHandler(reqRoute) match {
          case Some(handler) => {
            logger.debug("Processing with handler " + reqRoute.handlerName)
            
            handler(req, resp) match {
              case EmptyView => {
                logger.debug("Received a empty view. Will do no further output.")
              }
              case view: HandlerNameView => {
                logger.debug("Received a template view " + view.name)
                render(new View(reqRoute.handlerName, view.models: _*))
              }
              case view@ _ => {
                logger.debug("Received a template view " + view.name)
                render(view)
              }
            }
            
            //log processing time
            val t2 = JSystem.now
            logger.info("Request processed in " +(t2 - t1)+" millis seconds.")
          }
          case None => throw new Exception("Handler not found from " + reqRoute)
        }
      }
      case None => throw new Exception("Controller not found from " + reqRoute)
    }
  }
}
